#include "clib_ring.h"

#define U32_MAX 0xFFFFFFFF

i32_t clib_ring_init(clib_ring_t* ring, u32_t size, clib_ring_type_t type)
{
    memset(ring, 0, sizeof(clib_ring_t));
    ring->size = size;
    ring->type = type;

    return 0;
}

i32_t clib_ring_elem_size(clib_ring_t* ring)
{
    if(ring->tail > ring->head) {
        return U32_MAX - ring->tail + ring->head;
    } else {
        return ring->head - ring->tail;
    }
}

i32_t clib_ring_elem_empty(clib_ring_t* ring)
{
    if(ring->tail > ring->head) {
        return ring->size -  (U32_MAX - ring->tail + ring->head);
    } else {
        return ring->size - (ring->head - ring->tail);
    }
}

i32_t clib_ring_enqueue_elem(clib_ring_t* ring,void *obj)
{
    i32_t ret = 0;
    u32_t idx;
    if(ring->type) {
        clib_spinlock_lock(&ring->lock);
    }

    if(clib_ring_elem_empty(ring) > 1) {
        idx = ring->head % ring->size;
        ring->data[idx] = obj;
        ring->head++;
        ret += 1;
    }

    if(ring->type) {
        clib_spinlock_unlock(&ring->lock);
    }

    return ret;
}

i32_t clib_ring_enqueue_bulk_elem(clib_ring_t* ring,void **obj, u32_t size)
{
    i32_t ret = 0;
    i32_t empty;
    u32_t idx;
    u32_t copy;
    if(ring->type) {
        clib_spinlock_lock(&ring->lock);
    }

    empty = clib_ring_elem_empty(ring);
    idx = ring->head % ring->size;

    if(empty >= size) {
        ret = size;
    } else {
        ret = empty;
    }

    if(idx + ret >= ring->size) {
        copy = (ring->size - idx -1);
        memcpy(&ring->data[idx], obj, copy * sizeof(void*));
        memcpy(ring->data, &obj[copy], (ret - copy) * sizeof(void*));
    } else {
        memcpy(&ring->data[idx], obj, ret * sizeof(void*));
    }

    ring->head += ret;

    if(ring->type) {
        clib_spinlock_unlock(&ring->lock);
    }

    return ret;
}

i32_t clib_ring_dequeue_elem(clib_ring_t* ring, void **obj)
{
    i32_t ret = 0;
    u32_t idx;
    if(ring->type) {
        clib_spinlock_lock(&ring->lock);
    }

    if(clib_ring_elem_size(ring) > 1) {
        idx = ring->tail % ring->size;
        *obj = ring->data[idx];
        ring->tail++;
        ret += 1;
    }

    if(ring->type) {
        clib_spinlock_unlock(&ring->lock);
    }

    return ret;
}

i32_t clib_ring_dequeue_bulk_elem(clib_ring_t* ring,void **obj, u32_t size)
{
    i32_t ret = 0;
    i32_t elem;
    u32_t idx;
    u32_t copy;
    if(ring->type) {
        clib_spinlock_lock(&ring->lock);
    }

    elem = clib_ring_elem_size(ring);
    idx  = ring->tail % ring->size;

    if(elem >= size) {
        ret = size;
    } else {
        ret = elem;
    }

    if(idx + ret >= ring->size) {
        copy = (ring->size - idx -1);
        memcpy(obj, ring->data[idx],copy * sizeof(void*));
        memcpy(&obj[copy], &ring->data[idx], (ret - copy) * sizeof(void*));
    } else {
        memcpy(obj, &ring->data[idx], ret * sizeof(void*));
    }
    
    ring->tail += ret;

    if(ring->type) {
        clib_spinlock_unlock(&ring->lock);
    }

    return ret;
}